Saltar al contenido principal

Howl Audio Context

Introducción

En esta documentación explicaremos cómo utilizar Howler.js junto con React mediante un Context personalizado para manejar la reproducción y configuración de audio en tu aplicación. El objetivo es proporcionar una forma sencilla de reproducir, pausar, detener y controlar volúmenes de diferentes tipos de audio (acción, escena y voz) sin tener que replicar lógica en cada componente.


Tabla de Contenidos


Dependencias

Para poder utilizar esta implementación necesitas:

  • React (≥16.8)
  • Howler.js
  • Un bundler o sistema de empaquetado que permita importar archivos de audio (por ejemplo, Webpack o Vite).

Instalación rápida de Howler.js:

npm install howler
# o
yarn add howler


Constantes y Configuración Inicial

Antes de entrar a detalle con el contexto y el proveedor, definimos algunas constantes que servirán como base para el estado y la configuración de volúmenes por defecto:

// Constantes y configuración
export const AUDIO_STATUS = {
STOP: "stop",
START: "start",
PAUSE: "pause",
RESUME: "resume",
NONE: "n/a"
};

// Estado de carga de audio
export const LOAD_STATUS = {
UNLOADED: "unloaded",
LOADING: "loading",
READY: "ready",
ERROR: "error"
};

export const AUDIO_TYPES = {
ACTION: "action",
SCENE: "scene",
VOICE: "voice"
};

export const DEFAULT_VOLUMES = {
[AUDIO_TYPES.ACTION]: 0.3,
[AUDIO_TYPES.SCENE]: 0.03,
[AUDIO_TYPES.VOICE]: 0.85
};

Explicación Detallada

  1. AUDIO_STATUS

    • Define los diferentes estados que puede tener la reproducción de un audio.
    • "stop": el audio está detenido.
    • "start": el audio se acaba de iniciar.
    • "pause": el audio está en pausa.
    • "resume": el audio se está reanudando.
    • "n/a": estado inicial o sin asignar.
  2. LOAD_STATUS

    • Controla el estado de carga de cada archivo de audio.
    • "unloaded": aún no se ha iniciado la carga.
    • "loading": el archivo se está cargando.
    • "ready": el archivo está cargado y listo para reproducir.
    • "error": hubo un error al cargar el archivo.
  3. AUDIO_TYPES

    • Permite diferenciar los distintos tipos de audio en la aplicación:

      • ACTION → sonidos de acciones puntuales (por ejemplo, efectos).
      • SCENE → música o ambiente de fondo.
      • VOICE → narraciones o diálogos.
  4. DEFAULT_VOLUMES

    • Establece un volumen por defecto para cada tipo de audio.
    • Si el usuario no especifica un volumen distinto al reproducir, se usará el valor aquí definido.

Contexto (AudioHowlContext)

El contexto es el punto central que proporciona, mediante un Provider, todos los métodos y estados necesarios para controlar el audio de forma global.

import { createContext } from 'react';
import { LOAD_STATUS, AUDIO_TYPES } from './AudioHowlConst';

/**
* @typedef {Object} AudioController
* @property {number} volume - Volumen por defecto para este controlador
* @property {Object|null} howlInstance - Instancia de Howl que gestiona este audio
* @property {string} status - Estado actual de reproducción (basado en AUDIO_STATUS)
* @property {string} loadStatus - Estado actual de carga (basado en LOAD_STATUS)
* @property {string|null} currentSrc - Fuente actual del audio
*/

/**
* @typedef {Object} AudioConfigParams
* @property {boolean} [loop=false] - Indica si el audio debe reproducirse en bucle
* @property {number|null} [volume=null] - Volumen específico para esta reproducción
*/

/**
* @typedef {Object} PlayParams
* @property {string} type - Tipo de audio a reproducir (use AUDIO_TYPES)
* @property {string} audio - URL o recurso del audio a reproducir
* @property {AudioConfigParams} [configAudio] - Configuración de reproducción
*/

/**
* @typedef {Object} VolumeParams
* @property {string} type - Tipo de audio a ajustar (use AUDIO_TYPES)
* @property {number} volume - Nivel de volumen (0.0 a 1.0)
*/

/**
* @typedef {Object} PreloadParams
* @property {string} type - Tipo de audio a precargar (use AUDIO_TYPES)
* @property {string} audio - URL o recurso del audio a precargar
* @property {AudioConfigParams} [configAudio] - Configuración del audio
*/

// Valores por defecto para el contexto
const defaultContext = {
/**
* Reproduce un audio según el tipo especificado
* @param {PlayParams} params - Parámetros para reproducir el audio
* @returns {void}
*/
play: ({ type, audio, configAudio }) => { },

/**
* Detiene la reproducción de un tipo específico de audio
* @param {string} type - Tipo de audio a detener (use AUDIO_TYPES)
* @returns {void}
*/
stop: (type) => { },

/**
* Alterna entre pausa y reproducción para un tipo específico de audio
* @param {string} type - Tipo de audio a detener (use AUDIO_TYPES)
* @returns {void}
*/
togglePause: (type) => { },

/**
* Alterna el estado de silencio para todos los tipos de audio
* @returns {void}
*/
mute: () => { },

/**
* Establece el volumen para un tipo específico de audio
* @param {VolumeParams} params - Parámetros para ajustar el volumen
* @returns {void}
*/
setVolume: ({ type, volume }) => { },

/**
* Precarga un audio sin reproducirlo, para tenerlo disponible inmediatamente cuando se necesite
* @param {PreloadParams} params - Parámetros para precargar el audio
* @returns {string} Estado de carga actual
*/
preloadAudio: ({ type, audio, configAudio }) => LOAD_STATUS.UNLOADED,

/**
* Verifica si un audio específico ya está cargado y listo para reproducirse
* @param {string} type - Tipo de audio a verificar (use AUDIO_TYPES)
* @param {string} audioSrc - URL o recurso del audio a verificar
* @returns {boolean} true si el audio está cargado y listo
*/
isAudioLoaded: (type, audioSrc) => false,

/**
* Estado actual de carga para cada tipo de audio
* @type {Object.<string, string>}
*/
loadingStates: {
[AUDIO_TYPES.ACTION]: LOAD_STATUS.UNLOADED,
[AUDIO_TYPES.SCENE]: LOAD_STATUS.UNLOADED,
[AUDIO_TYPES.VOICE]: LOAD_STATUS.UNLOADED
},

/**
* Tipos de audio disponibles (ACTION, SCENE, VOICE)
* @type {Object.<string, string>}
*/
audioTypes: AUDIO_TYPES,

/**
* Constantes para estados de carga (UNLOADED, LOADING, READY, ERROR)
* @type {Object.<string, string>}
*/
loadStatus: LOAD_STATUS,

/**
* Controladores de audio para cada tipo
* @type {Object.<string, AudioController>}
*/
controllers: {
[AUDIO_TYPES.ACTION]: null,
[AUDIO_TYPES.SCENE]: null,
[AUDIO_TYPES.VOICE]: null
}
};

/**
* Contexto para la gestión de audio con Howler.js
* Proporciona métodos para reproducir, pausar, detener y controlar audio.
*
* @example
* // Ejemplo de uso básico
* const { play, stop, audioTypes } = useContext(AudioHowlContext);
*
* // Reproducir audio de fondo
* play({
* type: audioTypes.SCENE,
* audio: '/path/to/music.mp3',
* configAudio: { loop: true, volume: 0.5 }
* });
*
* // Detener audio
* stop(audioTypes.SCENE);
*/
const AudioHowlContext = createContext(defaultContext);

export default AudioHowlContext;

Explicación del Contexto

  1. Tipos de Parámetros (@typedef)

    • Se definen varios typedefs para documentar la forma de los objetos que usaremos:

      • AudioController: almacena la instancia Howl, estado y volumen de cada canal.
      • AudioConfigParams: opciones de configuración al reproducir (como loop y volume).
      • PlayParams, VolumeParams, PreloadParams: describen los parámetros que reciben las funciones del contexto.
  2. defaultContext

    • Contiene las funciones y estados predeterminados que tendrá el contexto antes de que el proveedor los reemplace con su implementación real.
    • Funciones vacías (play, stop, togglePause, etc.) que sirven como placeholders.
    • Valores de loadingStates inicializados en "unloaded".
    • Un objeto controllers inicial con null para cada tipo de audio, que luego será reemplazado por un controlador real en el proveedor.
  3. AudioHowlContext

    • Es el contexto creado mediante createContext(defaultContext).
    • Cualquier componente que haga useContext(AudioHowlContext) tendrá acceso a estas funciones y estados.

Proveedor (AudioHowlProvider)

El proveedor es donde se implementa la lógica real de gestión de audio usando Howler.js. A continuación detallamos su estructura y funcionamiento.

import React, { useCallback, useRef, useState } from 'react';
import { Howl } from 'howler';
import AudioHowlContext from './AudioHowlContext';
import { AUDIO_STATUS, AUDIO_TYPES, DEFAULT_VOLUMES, LOAD_STATUS } from './AudioHowlConst';

// Activación de audio en evento de click (para navegadores que lo requieren)
let isActiveAudio = false;
document.addEventListener('click', function () {
if (isActiveAudio) return;
console.log(`%c Audio permission granted`, `background: #222; color: #f6bfaf; font-size: 12px; padding: 5px;`);
isActiveAudio = true;
});

// Funciones auxiliares
const createEmptyController = (volume = 0) => ({
volume,
howlInstance: null,
status: AUDIO_STATUS.NONE,
loadStatus: LOAD_STATUS.UNLOADED,
currentSrc: null
});

const getEffectiveVolume = (requestedVolume, defaultVolume) => {
return requestedVolume ?? defaultVolume;
};

// Componente principal
const AudioHowlProvider = ({ children }) => {
// Estado de audio
const audioControllers = useRef({
[AUDIO_TYPES.ACTION]: createEmptyController(DEFAULT_VOLUMES[AUDIO_TYPES.ACTION]),
[AUDIO_TYPES.SCENE]: createEmptyController(DEFAULT_VOLUMES[AUDIO_TYPES.SCENE]),
[AUDIO_TYPES.VOICE]: createEmptyController(DEFAULT_VOLUMES[AUDIO_TYPES.VOICE])
});

// Estado para notificar cambios en la carga de audio
const [loadingStates, setLoadingStates] = useState({
[AUDIO_TYPES.ACTION]: LOAD_STATUS.UNLOADED,
[AUDIO_TYPES.SCENE]: LOAD_STATUS.UNLOADED,
[AUDIO_TYPES.VOICE]: LOAD_STATUS.UNLOADED
});

const isMuted = useRef(false);

// Verificar existencia de controlador de audio
const getActiveController = (type) => {
if (!type || !audioControllers.current[type]) return null;
const controller = audioControllers.current[type];
if (!controller.howlInstance) return null;
return controller;
};

// Actualizar estado de carga de un tipo de audio
const updateLoadStatus = (type, status) => {
if (!type || !audioControllers.current[type]) return;

audioControllers.current[type].loadStatus = status;

setLoadingStates(prevStates => ({
...prevStates,
[type]: status
}));
};

// Crear instancia de Howl con manejo de eventos de carga
const createHowlInstance = useCallback(({ type, src, loop = false, volume = 0 }) => {
if (!src || !type) return null;

updateLoadStatus(type, LOAD_STATUS.LOADING);

const instance = new Howl({
src: [src],
loop,
volume,
preload: true,
html5: false // Usar métodos Web Audio API para mejor rendimiento y control
});

// Eventos de carga
instance.once('load', () => {
updateLoadStatus(type, LOAD_STATUS.READY);
// console.log(`Audio ${type} loaded and ready`);
});

instance.once('loaderror', () => {
updateLoadStatus(type, LOAD_STATUS.ERROR);
// console.error(`Error loading audio ${type}`);
});

return instance;
}, []);

// Verificar si un audio ya está cargado con la misma fuente
const isAudioLoaded = useCallback((type, audioSrc) => {
if (!type || !audioControllers.current[type]) return false;

const controller = audioControllers.current[type];
return (
controller.howlInstance &&
controller.currentSrc === audioSrc &&
controller.loadStatus === LOAD_STATUS.READY
);
}, []);

// Reproducir audio
const play = useCallback(({
type,
audio,
configAudio = { loop: false, volume: null },
}) => {
if (!audio || !type || !audioControllers.current[type]) return;

const controller = audioControllers.current[type];
const { loop, volume } = configAudio;
const effectiveVolume = getEffectiveVolume(volume, controller.volume);

// Si el audio ya está cargado con la misma fuente
if (isAudioLoaded(type, audio)) {
if(controller.howlInstance.playing()) return
controller.status = AUDIO_STATUS.START;
controller.howlInstance.volume(effectiveVolume);
controller.howlInstance.mute(isMuted.current);
controller.howlInstance.play();
return;
}

// Actualizar estado
controller.status = AUDIO_STATUS.START;

// Limpiar instancia anterior
if (controller.howlInstance) {
controller.howlInstance.unload();
}

// Guardar referencia de la fuente actual
controller.currentSrc = audio;

// Crear y configurar nueva instancia
controller.howlInstance = createHowlInstance({
type,
src: audio,
loop,
volume: effectiveVolume
});

if (controller.howlInstance) {
controller.howlInstance.mute(isMuted.current);
controller.howlInstance.play();
}
}, [createHowlInstance, isAudioLoaded]);

// Precargar audio sin reproducirlo
const preloadAudio = useCallback(({
type,
audio,
configAudio = { loop: false, volume: null }
}) => {
if (!audio || !type || !audioControllers.current[type]) return;

// Si ya está cargado, no hacer nada
if (isAudioLoaded(type, audio)) {
return;
}

const controller = audioControllers.current[type];
const { loop, volume } = configAudio;
const effectiveVolume = getEffectiveVolume(volume, controller.volume);

// Limpiar instancia anterior
if (controller.howlInstance) {
controller.howlInstance.unload();
}

// Guardar referencia de la fuente actual
controller.currentSrc = audio;

// Crear instancia para precargar
controller.howlInstance = createHowlInstance({
type,
src: audio,
loop,
volume: effectiveVolume
});

return controller.loadStatus;
}, [createHowlInstance, isAudioLoaded]);

// Detener audio
const stop = useCallback((type) => {
const controller = getActiveController(type);
if (!controller) return;

controller.status = AUDIO_STATUS.STOP;
controller.howlInstance.stop();
}, []);

// Pausar/reanudar audio
const togglePause = useCallback((type) => {
const controller = getActiveController(type);
if (!controller) return;

if (![AUDIO_STATUS.STOP, AUDIO_STATUS.PAUSE].includes(controller.status)) {
controller.status = AUDIO_STATUS.PAUSE;
controller.howlInstance.pause();
} else {
controller.status = AUDIO_STATUS.RESUME;
controller.howlInstance.play();
}
}, []);

// Silenciar/activar sonido
const toggleMute = useCallback(() => {
const newMuteState = !isMuted.current;
isMuted.current = newMuteState;

Object.values(audioControllers.current).forEach(controller => {
if (controller.howlInstance) {
controller.howlInstance.mute(newMuteState);
}
});
}, []);

// Ajustar volumen
const setVolume = useCallback(({ type, volume }) => {
const controller = getActiveController(type);
if (!controller || volume === undefined) return;

const effectiveVolume = getEffectiveVolume(volume, controller.volume);
controller.howlInstance.volume(effectiveVolume);
}, []);

// API contexto
const contextValue = {
play,
stop,
togglePause,
mute: toggleMute,
setVolume,
preloadAudio,
isAudioLoaded,
loadingStates,
audioTypes: AUDIO_TYPES,
loadStatus: LOAD_STATUS,
controllers: audioControllers.current
};

return (
<AudioHowlContext.Provider value={contextValue}>
{children}
</AudioHowlContext.Provider>
);
};

export default AudioHowlProvider;

Explicación del Proveedor

  1. Activación del Audio en Navegadores Modernos

    • Algunos navegadores exigen un evento de interacción previa (por ejemplo, un click) para permitir la reproducción de audio.
    • Se escucha un click global y, la primera vez que suceda, se marca isActiveAudio = true.
  2. Funciones Auxiliares

    • createEmptyController(volume): crea un objeto controlador con estado inicial (sin instancia Howl, estado NONE, carga UNLOADED y volumen pasado por parámetro).
    • getEffectiveVolume(requestedVolume, defaultVolume): devuelve el volumen que se debe usar—si el usuario no especificó uno, usa el por defecto.
  3. Estados Internos

    • audioControllers (mediante useRef) contiene, para cada tipo de audio, un controlador construido con createEmptyController.
    • loadingStates (mediante useState) guarda únicamente el estado de carga de cada tipo para forzar re-render cuando cambie.
    • isMuted (mediante useRef) indica si actualmente todos los audios están silenciados.
  4. Métodos Principales

    • getActiveController(type): devuelve el controlador activo para el tipo especificado (o null si no existe o no tiene instancia Howl).

    • updateLoadStatus(type, status): cambia el estado de carga en el controlador y actualiza loadingStates para reflejarlo en la UI.

    • createHowlInstance({ type, src, loop, volume }): crea una nueva instancia Howl con la configuración indicada, registra eventos load y loaderror, y devuelve la instancia.

    • isAudioLoaded(type, audioSrc): comprueba si el controlador de ese tipo ya tiene cargada la misma fuente (currentSrc === audioSrc) y su estado de carga es READY.

    • play({ type, audio, configAudio }):

      • Si el audio ya está cargado (misma fuente y estado READY), revisa si no se esté reproduciendo; si no, ajusta volumen, mute y lo reproduce.

      • Si no está cargado o es una fuente distinta:

        1. Cambia estado a START.
        2. Si había una instancia previa, la descarga con .unload().
        3. Almacena la nueva fuente en currentSrc.
        4. Genera la instancia con createHowlInstance.
        5. Aplica estado mute y llama a .play().
    • preloadAudio({ type, audio, configAudio }):

      • Si ya está cargado, no hace nada.
      • Descarga instancia previa (si existía), guarda la nueva fuente, crea instancia (sin llamar a .play()).
      • Devuelve el estado de carga actual (que al llamar a createHowlInstance pasará de LOADING a READY o ERROR).
    • stop(type): detiene la reproducción y cambia status a STOP.

    • togglePause(type): si el audio está reproduciéndose, lo pausa (cambia a PAUSE); si está en STOP o PAUSE, lo reanuda (cambia a RESUME).

    • toggleMute(): invierte el estado de isMuted y lo aplica a todas las instancias Howl de los controladores existentes.

    • setVolume({ type, volume }): ajusta el volumen de la instancia Howl del tipo dado, usando getEffectiveVolume para determinar el valor final.

  5. Valor del Contexto (contextValue)

    • Agrupa todas las funciones (play, stop, togglePause, mute, setVolume, preloadAudio, isAudioLoaded) y estados (loadingStates, audioTypes, loadStatus, controllers) que estarán disponibles para cualquier componente que consuma el contexto.

Hook Personalizado (useAudioHowl)

Para simplificar el acceso al contexto en los componentes, se define un hook que verifica que se encuentre dentro de un AudioHowlProvider:

import { useContext } from 'react';
import AudioHowlContext from './AudioHowlContext';

// Hook personalizado para consumir el contexto
const useAudioHowl = () => {
const context = useContext(AudioHowlContext);
if (!context) {
throw new Error('useAudioHowl debe usarse dentro de un AudioHowlProvider');
}
return context;
};

export default useAudioHowl;

Explicación

  • useAudioHowl: simplemente llama a useContext(AudioHowlContext).
  • Si se invoca fuera de un proveedor, arroja un error para advertir al desarrollador que el contexto no está disponible.

Ejemplo de Uso

A continuación vemos un componente de ejemplo (SubTestAudio) que demuestra un flujo típico de carga y reproducción de audio:

import React, { useEffect, useState } from 'react';
import useAudioHowl from '@lib/audioHowl/useAudioHowl';
import audio from '@assets/audio/testScene.mp3';
import audio2 from '@assets/audio/testScene2.mp3';
import sourceAudio from '@assets/audio/testSound.wav';

const SubTestAudio = () => {
const {
loadingStates,
loadStatus,
audioTypes,
play,
preloadAudio,
isAudioLoaded,
mute,
togglePause,
stop,
setVolume
} = useAudioHowl();

const [sceneAudioState, setSceneAudioState] = useState('No cargado');

// Cargar audio principal al montar el componente
useEffect(() => {
play({ type: audioTypes.SCENE, audio, configAudio: { loop: true } });
}, [play, audioTypes]);

// Monitorear estados de carga
useEffect(() => {
// Escuchar cambios en el estado de carga del audio "scene"
const currentLoadState = loadingStates[audioTypes.SCENE];

switch (currentLoadState) {
case loadStatus.LOADING:
setSceneAudioState('Cargando...');
break;
case loadStatus.READY:
setSceneAudioState('Listo para reproducir');
break;
case loadStatus.ERROR:
setSceneAudioState('Error al cargar');
break;
default:
setSceneAudioState('No cargado');
}
}, [loadingStates, audioTypes, loadStatus]);

// Precarga del audio2 para tenerlo disponible rápidamente
useEffect(() => {
// Precargar el segundo audio sin reproducirlo
preloadAudio({
type: audioTypes.ACTION,
audio: sourceAudio,
configAudio: { volume: 1 }
});
}, [preloadAudio, audioTypes]);

const handlePlaySecondAudio = () => {
// Comprobar si ya está cargado antes de reproducir
if (isAudioLoaded(audioTypes.ACTION, sourceAudio)) {
// "¡Audio ya cargado! Reproduciendo inmediatamente";
} else {
// "Cargando audio antes de reproducir...";
}

play({
audio: sourceAudio,
type: audioTypes.ACTION,
configAudio: { volume: 1 }
});
};

return (
<div className="fixed z-20 w-full bottom-10 left-0">
<div className='w-full h-full bg-amber-200 flex flex-col justify-center items-center gap-2'>
<div className='text-center mb-4'>
Estado del audio: <strong>{sceneAudioState}</strong>
</div>

<div className='flex justify-center items-center gap-2'>
<div onClick={() => togglePause(audioTypes.SCENE)} className='w-20 h-20 bg-red-400 flex items-center justify-center cursor-pointer'>Pausa</div>
<div onClick={() => mute()} className='w-20 h-20 bg-red-400 flex items-center justify-center cursor-pointer'>Mute</div>
<div onClick={() => stop(audioTypes.SCENE)} className='w-20 h-20 bg-red-400 flex items-center justify-center cursor-pointer'>Stop</div>
<div onClick={() => setVolume({ type: audioTypes.SCENE, volume: 1 })} className='w-20 h-20 bg-red-400 flex items-center justify-center cursor-pointer'>Volumen</div>
<div onClick={handlePlaySecondAudio} className='w-20 h-20 bg-red-400 flex items-center justify-center cursor-pointer'>Play Audio</div>
<div onClick={() => play({ audio: audio2, type: audioTypes.SCENE, configAudio: { loop: false, volume: 0.5 } })} className='w-20 h-20 bg-red-400 flex items-center justify-center cursor-pointer'>Play scene</div>
</div>
</div>
</div>
);
};

export default SubTestAudio;

Desglose del Componente

  1. Importaciones

    • Se importa el hook useAudioHowl para acceder a las funciones de reproducción y estado de carga.
    • Se importan tres archivos de audio (testScene.mp3, testScene2.mp3 y testSound.wav) desde la carpeta de assets.
  2. Desestructuración del Hook

    const {
    loadingStates,
    loadStatus,
    audioTypes,
    play,
    preloadAudio,
    isAudioLoaded,
    mute,
    togglePause,
    stop,
    setVolume
    } = useAudioHowl();
    • loadingStates: objeto con el estado de carga para cada tipo de audio.
    • loadStatus: constantes para comparar (LOADING, READY, ERROR, etc.).
    • audioTypes: constantes de tipo de audio (ACTION, SCENE, VOICE).
    • play, preloadAudio, isAudioLoaded, mute, togglePause, stop, setVolume: funciones para controlar el audio.
  3. Estado Local (sceneAudioState)

    • Almacena un texto descriptivo del estado de carga del audio de escena.
    • Inicialmente "No cargado".
  4. Primer useEffect – Reproducción Automática

    useEffect(() => {
    play({ type: audioTypes.SCENE, audio, configAudio: { loop: true } });
    }, [play, audioTypes]);
    • Al montarse el componente, llama a play para iniciar la reproducción del audio de tipo SCENE en bucle.
  5. Segundo useEffect – Monitoreo de Carga

    useEffect(() => {
    const currentLoadState = loadingStates[audioTypes.SCENE];
    switch (currentLoadState) {
    case loadStatus.LOADING:
    setSceneAudioState('Cargando...');
    break;
    case loadStatus.READY:
    setSceneAudioState('Listo para reproducir');
    break;
    case loadStatus.ERROR:
    setSceneAudioState('Error al cargar');
    break;
    default:
    setSceneAudioState('No cargado');
    }
    }, [loadingStates, audioTypes, loadStatus]);
    • Escucha los cambios en loadingStates[SCENE] para actualizar el texto que indica si el audio está cargando, listo o con error.
  6. Tercer useEffect – Precarga de Audio de Acción

    useEffect(() => {
    preloadAudio({
    type: audioTypes.ACTION,
    audio: sourceAudio,
    configAudio: { volume: 1 }
    });
    }, [preloadAudio, audioTypes]);
    • Precarga testSound.wav para que, cuando se llame a reproducción (por ejemplo, al presionar un botón), no haya latencia de carga.
  7. Función handlePlaySecondAudio

    const handlePlaySecondAudio = () => {
    if (isAudioLoaded(audioTypes.ACTION, sourceAudio)) {
    // Audio ya cargado ⇒ Reproducción inmediata
    } else {
    // Mostrar mensaje de carga
    }

    play({
    audio: sourceAudio,
    type: audioTypes.ACTION,
    configAudio: { volume: 1 }
    });
    };
    • Comprueba si el audio de acción ya está cargado (isAudioLoaded).
    • Llama a play independientemente, pero si no estaba cargado, se mostrará “Cargando...” antes de reproducir.
  8. Renderizado de la Interfaz

    • Un div fijo en la parte inferior de la pantalla con botones para:

      • Pausar/reanudar (togglePause).
      • Silenciar/activar todo (mute).
      • Detener audio de escena (stop).
      • Ajustar volumen al máximo (setVolume).
      • Reproducir el segundo audio (handlePlaySecondAudio).
      • Reproducir otro audio de escena (play({ audio2 })).

Descargar el Paquete

Si quieres utilizar esta implementación en tu proyecto, puedes descargar el ZIP con el código completo desde:

Descargar LibAudioHowl.zip

Dentro del ZIP encontrarás la siguiente estructura:

LibAudioHowl/
├── src/
│ ├── AudioHowlConst.js
│ ├── AudioHowlContext.js
│ ├── AudioHowlProvider.js
│ ├── useAudioHowl.js
│ └── components/
│ └── SubTestAudio.jsx
├── package.json
└── README.md
  • AudioHowlConst.js: Define las constantes (AUDIO_STATUS, LOAD_STATUS, AUDIO_TYPES, DEFAULT_VOLUMES).
  • AudioHowlContext.js: Contiene el contexto y typedefs para documentar los parámetros.
  • AudioHowlProvider.js: Implementa la lógica de Howler.js y expone la API al contexto.
  • useAudioHowl.js: Hook personalizado para acceder fácilmente al contexto.
  • components/SubTestAudio.jsx: Componente de prueba que muestra un ejemplo de uso.

y agrega al inicio de tu APP


import './App.css'
import { RouterApp } from "./router";
import AudioHowlProvider from '@lib/audioHowl/AudioHowlProvider';

function App() {
return (
<AudioHowlProvider>
<RouterApp />
</AudioHowlProvider>
)
}

export default App